﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Diagnostics;
using Blade.Metadata.CliLoader;
using Blade.IO;

namespace Blade.Metadata.PeLoader {
	sealed class PeHeader {
		public PeHeader(BinaryReader reader, IMetadataLoaderContext context) {
			#region file header (§II.25.2.2)
			this.Machine = reader.ReadUInt16(); context.Assert(() => Machine == 0x14C);
			var sectionCount = reader.ReadUInt16();
			this.TimeStamp = reader.ReadUInt32();
			this.SymbolTablePointer = reader.ReadUInt32(); context.Assert(() => SymbolTablePointer == 0);
			this.SymbolCount = reader.ReadUInt32(); context.Assert(() => SymbolCount == 0);
			this.OptionalHeaderSize = reader.ReadUInt16();
			this.Characteristics = reader.ReadUInt16(); // HACK: context.Assert(() => (Characteristics & ~0x2000) == (0x100 | 0x002 | 0x004 | 0x008));
			#endregion

			#region optional header (§II.25.2.3)
			#region standard fields (§II.25.2.3.1)
			this.Magic = reader.ReadUInt16(); context.Assert(() => Magic == 0x10B);
			this.LMajor = reader.ReadByte(); //HACK: context.Assert(() => LMajor == 6);
			this.LMinor = reader.ReadByte(); //HACK: context.Assert(() => LMinor == 0);
			this.CodeSize = reader.ReadUInt32();
			this.InitDataSize = reader.ReadUInt32();
			this.UnintDataSize = reader.ReadUInt32();
			this.EntryPointRVA = reader.ReadUInt32();
			this.BaseOfCode = reader.ReadUInt32();
			this.BaseOfData = reader.ReadUInt32();
			#endregion
			#region nt-specific fields (§II.25.2.3.2)
			this.ImageBase = reader.ReadUInt32(); context.Assert(() => ImageBase == 0x400000);
			this.SectionAlign = reader.ReadUInt32(); context.Assert(() => SectionAlign == 0x2000);
			this.FileAlign = reader.ReadUInt32(); context.Assert(() => FileAlign == 0x200 || FileAlign == 0x1000);
			this.OSMajor = reader.ReadUInt16(); context.Assert(() => OSMajor == 4);
			this.OSMinor = reader.ReadUInt16(); context.Assert(() => OSMinor == 0);
			this.UserMajor = reader.ReadUInt16(); context.Assert(() => UserMajor == 0);
			this.UserMinor = reader.ReadUInt16(); context.Assert(() => UserMinor == 0);
			this.SubSysMajor = reader.ReadUInt16(); context.Assert(() => SubSysMajor == 4);
			this.SubSysMinor = reader.ReadUInt16(); context.Assert(() => SubSysMinor == 0);
			var reserved = reader.ReadUInt32(); context.Assert(() => reserved == 0);
			this.ImageSize = reader.ReadUInt32();
			this.HeaderSize = reader.ReadUInt32();
			this.FileChecksum = reader.ReadUInt32(); context.Assert(() => FileChecksum == 0);
			this.SubSystem = reader.ReadUInt16(); context.Assert(() => SubSystem == 0x2 || SubSystem == 0x3);
			this.DllFlags = reader.ReadUInt16(); //HACK: context.Assert(() => DllFlags == 0);
			this.StackReserveSize = reader.ReadUInt32(); context.Assert(() => StackReserveSize == 0x100000);
			this.StackCommitSize = reader.ReadUInt32(); context.Assert(() => StackCommitSize == 0x1000);
			this.HeadReserveSize = reader.ReadUInt32(); context.Assert(() => HeadReserveSize == 0x100000);
			this.HeapCommitSize = reader.ReadUInt32(); context.Assert(() => HeapCommitSize == 0x1000);
			this.LoaderFlags = reader.ReadUInt32(); context.Assert(() => LoaderFlags == 0);
			var dataDirectoryCount = reader.ReadUInt32(); context.Assert(() => dataDirectoryCount == 0x10);
			#endregion
			this.DataDirectory = new RVAPair[dataDirectoryCount];
			for (int i = 0; i < dataDirectoryCount; i++) {
				DataDirectory[i] = new RVAPair(reader);
			}
			#endregion
			this.SectionHeaders = new PeSectionHeader[sectionCount];
			for (int i = 0; i < sectionCount; i++) {
				SectionHeaders[i] = new PeSectionHeader(reader);
			}
		}

		#region file header (§II.25.2.2)
		public readonly ushort Machine;
		//public readonly ushort SectionCount;
		public readonly uint TimeStamp;
		public readonly uint SymbolTablePointer;
		public readonly uint SymbolCount;
		public readonly ushort OptionalHeaderSize;
		public readonly ushort Characteristics;
		#endregion

		// note: optional header is required by the CLI
		#region optional header (§II.25.2.3)
		#region standard fields (§II.25.2.3.1)
		public readonly ushort Magic; // == 0x10B
		public readonly byte LMajor; // == 6
		public readonly byte LMinor; // == 0
		public readonly uint CodeSize;
		public readonly uint InitDataSize;
		public readonly uint UnintDataSize;
		public readonly uint EntryPointRVA;
		public readonly uint BaseOfCode;
		public readonly uint BaseOfData;
		#endregion
		#region nt-specific fields (§II.25.2.3.2)
		public readonly uint ImageBase; // == 0x400000
		public readonly uint SectionAlign; // == 0x2000
		public readonly uint FileAlign; // == 0x200 || == 0x1000
		public readonly ushort OSMajor; // == 4
		public readonly ushort OSMinor; // == 0
		public readonly ushort UserMajor; // == 0
		public readonly ushort UserMinor; // == 0
		public readonly ushort SubSysMajor; // == 4
		public readonly ushort SubSysMinor; // == 0
		//uint Reserved;
		public readonly uint ImageSize;
		public readonly uint HeaderSize;
		public readonly uint FileChecksum; // == 0
		public readonly ushort SubSystem; // == 0x2 (GUI) || == 0x3 (CE_GUI)
		public readonly ushort DllFlags; // == 0
		public readonly uint StackReserveSize; // == 0x100000
		public readonly uint StackCommitSize; // == 0x1000
		public readonly uint HeadReserveSize; // == 0x100000
		public readonly uint HeapCommitSize; // == 0x1000
		public readonly uint LoaderFlags; // == 0
		//public readonly uint DataDirectoryCount; // == 0x10
		#endregion
		public readonly RVAPair[] DataDirectory; // List of Data Directory Entries §II.25.2.3.3
		#endregion

		public readonly PeSectionHeader[] SectionHeaders;
	}
}
